package com.guardian.command;

import com.guardian.constant.SystemConstant;
import com.guardian.exception.InitializationException;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 命令执行对象加载器
 * @author: huwei
 * @date: 2019/10/10 14:00
 * @version: 1.0.0
 */
public class CommandLoader {

    /**
     * 执行对象包路径
     */
    private  String executorPackage;
    /**
     * 类后缀名
     */
    private static final String CLASS_SUFFIX_NAME = ".class";
    /**
     * 类路径分割服务
     */
    private static final String PACKAGE_SPLIT_SYMBOL = ".";

    /**
     *
     * @param packageName command对象的包名，该包名相对于com.guardian.command包名之下；示例：startup ，即加载com.guardian.command.startup包下的Command对象
     */
    public CommandLoader(String packageName){
        String commandExecutorSimpleName = Command.class.getSimpleName();
        String commandExecutorName = Command.class.getName();
        executorPackage = commandExecutorName.substring(0 ,commandExecutorName.indexOf(commandExecutorSimpleName));
        executorPackage = executorPackage.concat(packageName);
    }

    /**
     * 将指定包名下的Command对象加载到内存中
     * @return 返回加载到内存中的Command对象集合
     * @throws InitializationException 加载失败，跑出异常
     */
    public Set<Command> load() throws InitializationException {
        Set<Class<? extends Command>> classSet =  loadClass();

        Set<Command> commandExecutorSet = new HashSet<>(classSet.size());
        for(Class<? extends Command> cls : classSet){
            try {
                Command executor = cls.newInstance();
                commandExecutorSet.add(executor);
            }catch (Exception e){
                throw new InitializationException("系统初始化失败，加载命令执行器错误!!" ,e);
            }
        }
        return commandExecutorSet;
    }

    private Set<Class<? extends Command>> loadClass(){
        File file = new File(SystemConstant.SYSTEM_HOME_DIRECTORY);
        File[] jarFiles = file.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar");
            }
        });

        if(jarFiles.length == 0){
            return loadClassInClasspath();
        }
        return loadClassInJarFiles(jarFiles);
    }

    private Set<Class<? extends Command>> loadClassInClasspath() {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        File exeDir = new File(classpath.concat(File.separator).concat(executorPathName()));
        String[] classNames = exeDir.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(CLASS_SUFFIX_NAME);
            }
        });

        Set<Class<? extends Command>> executorSet = new HashSet<Class<? extends Command>>(classNames.length);
        for (String className : classNames) {
            Class<? extends Command> cls = loadExecutorClass(classpathName(className));
            if(cls != null){
                executorSet.add(cls);
            }
        }
        return executorSet;
    }

    private String executorPathName() {
        return executorPackage.replace("." ,File.separator);
    }

    private Set<Class<? extends Command>> loadClassInJarFiles(File[] jarFiles) {
        List<String> jarClassNameList = new ArrayList<String>(5);
        for (File jarFile : jarFiles) {
            JarFile jf = null;
            try {
                jf = new JarFile(jarFile);
                Enumeration<JarEntry> enums = jf.entries();
                while (enums.hasMoreElements()) {
                    JarEntry element = enums.nextElement();
                    String name = element.getName();
                    if (name.replace("/" ,".").startsWith(executorPackage)){
                        jarClassNameList.add(name);
                    }
                }
            } catch (IOException e) {
            }
        }

        Set<Class<? extends Command>> executorSet = new HashSet<Class<? extends Command>>(5);
        jarClassNameList.forEach(x->{
            Class<? extends Command> cls = loadExecutorClass(classpathName(x));
            if(cls != null){
                executorSet.add(cls);
            }
        });

        return executorSet;
    }

    private Class<? extends Command> loadExecutorClass(String executorClassPathName){
        try {
            Class<?> cls = ClassLoader.getSystemClassLoader().loadClass(executorClassPathName);
            if(Command.class.isAssignableFrom(cls)){
                return (Class<? extends Command>) cls;
            }
        } catch (ClassNotFoundException e) {
        }
        return null;
    }

    private String classpathName(String className){
        if(className.endsWith(CLASS_SUFFIX_NAME)){
            className = className.substring(0 ,className.indexOf(CLASS_SUFFIX_NAME));
        }
        String separator = "/";
        if(className.indexOf(separator) == -1){
            separator = "\\";
        }
        String classpathName = className.replace(separator,PACKAGE_SPLIT_SYMBOL);
        if(classpathName.startsWith(executorPackage)){
            return classpathName;
        }
        return executorPackage.concat(PACKAGE_SPLIT_SYMBOL).concat(classpathName);
    }
}
